package gu.sql2java.json;

import static gu.sql2java.utils.CaseSupport.isSnakecase;
import static gu.sql2java.utils.CaseSupport.toCamelcase;

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.BasicBeanDescription;
import gu.sql2java.BaseBean;
import gu.sql2java.Constant;

/**
 * implementation of BaseBean class for 
 * Jackson {@link JsonDeserializer},<br>
 * MAVEN dependencies:
 * <pre>
 *		&lt;dependency&gt;
 *			&lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
 *			&lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
 *			&lt;version&gt;${jackson.version}&lt;/version&gt;
 *			&lt;scope&gt;provided&lt;/scope&gt;
 *		&lt;/dependency&gt;
 * </pre>
 * 
 * @author guyadong
 * @since 3.21.2
 */
public class JacksonDeserializer extends BeanDeserializer implements Constant  {
	private static final long serialVersionUID = 7410414787512241455L;
	public JacksonDeserializer(Class<? extends BaseBean> beanClass) {
		super(createBeanDeserializer(beanClass));
	}

	@Override
	public BaseBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
		// see also BeanDeserializer.vanillaDeserialize
		BaseBean bean = (BaseBean) _valueInstantiator.createUsingDefault(ctxt);
		Boolean _new = null;
		Integer initialized = null,modified = null;
		
//        if (! jp.isExpectedStartObjectToken()) {
//        	throw new JsonParseException(jp,"NOT FOUND " + JsonToken.START_OBJECT,jp.getCurrentLocation());
//        }
        for(String propName = jp.nextFieldName();propName != null;propName = jp.nextFieldName()) {
        	jp.nextToken();
        	SettableBeanProperty prop = findProperty(this,propName);
        	if (prop != null) { // normal case
        		try {
        			switch(propName) {
        			case FIELD_NEW:
        				_new = (Boolean) prop.deserialize(jp,ctxt);
        				break;
        			case FIELD_INITIALIZED:
        				initialized = (Integer) prop.deserialize(jp,ctxt);
        				break;
        			case FIELD_MODIFIED:
        				modified = (Integer) prop.deserialize(jp,ctxt);
        				break;
        			default:
        				prop.deserializeAndSet(jp, ctxt, bean);
        			}
        		} catch (Exception e) {
        			wrapAndThrow(e, bean, propName, ctxt);
        		}
        		continue;
        	}
            handleUnknownVanilla(jp, ctxt, bean, propName);
        }
        if(null != _new) {
        	bean.setNew(_new);
        }
		if(null != initialized){
			bean.setInitialized(initialized);
		}
		if(null != modified){
			bean.setModified(modified);
		}
		return bean;
	}
	/**
	 * 查找字段名对应的{@link SettableBeanProperty}对象如果没找到且字段名为snake-case尝试转为camel-case查找。
	 * @param beanDeserializer
	 * @param propName
	 * @see BeanDeserializer#findProperty(String)
	 */
	private SettableBeanProperty findProperty(BeanDeserializer beanDeserializer,String propName) {
		SettableBeanProperty prop = beanDeserializer.findProperty(propName);
		if(null == prop && isSnakecase(propName)) {
			prop = beanDeserializer.findProperty(toCamelcase(propName));
		}
		return prop;
	}
	/**
	 * 创建{@code beanClass}对应的{@link BeanDeserializerBase}实例用于父类构造方法的参数,
	 * 将{@code beanClass}的序列化参数注入到当前实例中
	 * @param beanClass
	 */
	private static BeanDeserializer createBeanDeserializer(Class<?> beanClass){
		try {
			ObjectMapper mapper = new ObjectMapper();
			DefaultDeserializationContext defctx = (DefaultDeserializationContext) mapper.getDeserializationContext();
			DefaultDeserializationContext ctxt = defctx.createInstance(mapper.getDeserializationConfig(),	null,null);
			JavaType type = ctxt.constructType(beanClass);
			BasicBeanDescription beanDesc = (BasicBeanDescription)ctxt.getConfig().introspect(type);
			BeanDeserializerFactory factory = (BeanDeserializerFactory) ctxt.getFactory();
			BeanDeserializer beanDeserializer = (BeanDeserializer) factory.buildBeanDeserializer(ctxt, type, beanDesc);
			beanDeserializer.resolve(ctxt);
			return beanDeserializer;
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
}
